package com.nulldev.util.networking.async2.tcp.client;

import java.net.InetAddress;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.nulldev.util.data.Arrays.arrays.FastLists;
import com.nulldev.util.networking.async2.DataFrame;
import com.nulldev.util.networking.async2.executors.ASync2ThreadPool;
import com.nulldev.util.networking.async2.tcp.client.connectors.TCPConnector;
import com.nulldev.util.networking.async2.tcp.client.exceptions.ConnectorCannotSendPacketException;
import com.nulldev.util.networking.async2.tcp.client.exceptions.ConnectorDisconnectedException;

public class Async2TCPClient {

	private final String fName, fDescription;
	private final List<TCPConnector> fConnectors = FastLists.list();
	private final ArrayBlockingQueue<Runnable> fThreadPoolQueue = new ArrayBlockingQueue<Runnable>(200000);
	public final ASync2ThreadPool fMyThreadPool;

	/**
	 * Constructs a new Extasys TCP Client.
	 *
	 * @param name            is the name of the client.
	 * @param description     is the description of the client.
	 * @param corePoolSize    is the number of threads to keep in the pool, even if
	 *                        they are idle.
	 * @param maximumPoolSize is the maximum number of threads to allow in the pool.
	 */
	public Async2TCPClient(String name, String description, int corePoolSize, int maximumPoolSize) {
		fName = name;
		fDescription = description;
		fMyThreadPool = new ASync2ThreadPool(corePoolSize, maximumPoolSize, corePoolSize, TimeUnit.SECONDS, fThreadPoolQueue);
	}

	/**
	 * Add a new connector to this client.
	 *
	 * @param name           is the connector's name.
	 * @param serverIP       is the remote host's (server) IP address.
	 * @param serverPort     is the remote host's (server) port.
	 * @param readBufferSize is the read buffer size for this connection in bytes.
	 * @return the connector.
	 */
	public TCPConnector addConnector(String name, InetAddress serverIP, int serverPort, int readBufferSize) {
		TCPConnector connector = new TCPConnector(this, name, serverIP, serverPort, readBufferSize);
		fConnectors.add(connector);
		return connector;
	}

	/**
	 * Add a new connector with message collector.
	 *
	 * @param name           is the connector's name.
	 * @param serverIP       is the remote host's (server) IP address.
	 * @param serverPort     is the remote host's (server) port.
	 * @param readBufferSize is the read buffer size for this connection in bytes.
	 * @param ETX            is the End of Text character.
	 * @return the connector.
	 */
	public TCPConnector addConnector(String name, InetAddress serverIP, int serverPort, int readBufferSize, char ETX) {
		TCPConnector connector = new TCPConnector(this, name, serverIP, serverPort, readBufferSize, ETX);
		fConnectors.add(connector);
		return connector;
	}

	/**
	 * Add a new connector with message collector.
	 *
	 * @param name           is the connector's name.
	 * @param serverIP       is the remote host's (server) IP address.
	 * @param serverPort     is the remote host's (server) port.
	 * @param readBufferSize is the read buffer size for this connection in bytes.
	 * @param splitter       is the message splitter.
	 * @return the connector.
	 */
	public TCPConnector addConnector(String name, InetAddress serverIP, int serverPort, int readBufferSize, String splitter) {
		TCPConnector connector = new TCPConnector(this, name, serverIP, serverPort, readBufferSize, splitter);
		fConnectors.add(connector);
		return connector;
	}

	/**
	 * Stop and remove a connector from this client.
	 *
	 * @param name is the connector's name.
	 */
	public void removeConnector(String name) {
		for (int i = 0; i < fConnectors.size(); i++) {
			if (((TCPConnector) fConnectors.get(i)).getName().equals(name)) {
				((TCPConnector) fConnectors.get(i)).stop();
				fConnectors.remove(i);
				break;
			}
		}
	}

	/**
	 * Start or restart the client.
	 *
	 * @throws java.lang.Exception
	 */
	public void start() throws Exception {
		stop();

		try {
			for (int i = 0; i < fConnectors.size(); i++) {
				((TCPConnector) fConnectors.get(i)).start();
			}
		} catch (Exception ex) {
			stop();
			throw ex;
		}
	}

	/**
	 * Stop the client.
	 */
	public void stop() {
		stop(false);
	}

	/**
	 * Force client to stop.
	 */
	public void forceStop() {
		stop(true);
	}

	private void stop(boolean force) {
		for (int i = 0; i < fConnectors.size(); i++) {
			if (!force) {
				((TCPConnector) fConnectors.get(i)).stop();
			} else {
				((TCPConnector) fConnectors.get(i)).forceStop();
			}
		}

		try {
			fMyThreadPool.getQueue().clear();
		} catch (Exception ex) {
		}
	}

	/**
	 * Dispose the client. This method stops the client and disposes all the active
	 * members of this class. After calling this method you cannot re-start the
	 * client.
	 */
	public void dispose() {
		stop();
		fMyThreadPool.shutdown();
	}

	/**
	 * Send data from all connector's to all hosts.
	 *
	 * @param data is the string to be send.
	 * @throws Extasys.Network.TCP.Client.Exceptions.ConnectorCannotSendPacketException
	 * @throws Extasys.Network.TCP.Client.Exceptions.ConnectorDisconnectedException
	 */
	public void sendData(String data) throws ConnectorDisconnectedException, ConnectorCannotSendPacketException {
		for (int i = 0; i < fConnectors.size(); i++) {
			((TCPConnector) fConnectors.get(i)).sendData(data);
		}
	}

	/**
	 * Send data from all connector's to all hosts.
	 *
	 * @param bytes  is the byte array to be send.
	 * @param offset is the position in the data buffer at witch to begin sending.
	 * @param length is the number of the bytes to be send.
	 * @throws Extasys.Network.TCP.Client.Exceptions.ConnectorDisconnectedException
	 * @throws Extasys.Network.TCP.Client.Exceptions.ConnectorCannotSendPacketException
	 */
	public void sendData(byte[] bytes, int offset, int length) throws ConnectorDisconnectedException, ConnectorCannotSendPacketException {
		for (int i = 0; i < fConnectors.size(); i++) {
			((TCPConnector) fConnectors.get(i)).sendData(bytes, offset, length);
		}
	}

	/**
	 * A connector of this client receives data.
	 *
	 * @param connector is the client's connector.
	 * @param data      is the received data.
	 */
	public void onDataReceive(TCPConnector connector, DataFrame data) {
		// System.out.println(new String(data.getBytes()));
	}

	/**
	 * A connector of this client connected to a server.
	 *
	 * @param connector
	 */
	public void onConnect(TCPConnector connector) {
		// System.out.println("Connector " + connector.getName() + " connected!");
	}

	/**
	 * A connector of this client has been disconnected.
	 *
	 * @param connector
	 */
	public void onDisconnect(TCPConnector connector) {
		// System.out.println("Connector " + connector.getName() + " disconnected!");
	}

	/**
	 * Return the name of the client.
	 *
	 * @return the name of the client.
	 */
	public String getName() {
		return fName;
	}

	/**
	 * Return the description of the client.
	 *
	 * @return the description of the client.
	 */
	public String getDescription() {
		return fDescription;
	}

	/**
	 * Return ArrayList with the client's connectors. Each element of the ArrayList
	 * is a TCPConnector class.
	 *
	 * @return ArrayList with the client's connectors.
	 */
	public List<TCPConnector> getConnectors() {
		return fConnectors;
	}

	/**
	 * Return the client's Thread Pool.
	 *
	 * @return the client's Thread Pool.
	 */
	public ThreadPoolExecutor getMyThreadPool() {
		return fMyThreadPool;
	}

	/**
	 * Returns the total number of bytes received from all the connectors of the
	 * client.
	 *
	 * @return the number of bytes received from all the connectors of the client.
	 */
	public long getBytesIn() {
		long bytesIn = 0;
		try {
			for (int i = 0; i < fConnectors.size(); i++) {
				bytesIn += ((TCPConnector) fConnectors.get(i)).getBytesIn();
			}
		} catch (Exception ex) {
		}
		return bytesIn;
	}

	/**
	 * Returns the total number of bytes send from all the connectors of the client.
	 *
	 * @return the total number of bytes send from all the connectors of the client.
	 */
	public long getBytesOut() {
		long bytesOut = 0;
		try {
			for (int i = 0; i < fConnectors.size(); i++) {
				bytesOut += ((TCPConnector) fConnectors.get(i)).getBytesOut();
			}
		} catch (Exception ex) {
		}
		return bytesOut;
	}
}